package org.soc.timesheet.custom.interceptor;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Hashtable;
import java.util.Map;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.soc.timesheet.custom.annotation.AOPLogger;
import org.soc.timesheet.custom.annotation.AOPLogger.LoggerLevel;
import org.soc.timesheet.custom.annotation.AOPLogger.LoggerWhen;

public aspect LoggerInterceptor {

	pointcut logger() : execution( * (@AOPLogger * ).*(..)) || execution(@AOPLogger * *(..));

	pointcut loggerMethod() : execution(@org.soc.timesheet.custom.annotation.AOPLogger * *(..));

	static Map<LoggerLevel, Method> mthMap = new Hashtable<AOPLogger.LoggerLevel, Method>();
	static Map<LoggerLevel, Method> mthMapError = new Hashtable<AOPLogger.LoggerLevel, Method>();

	static Map<LoggerLevel, Integer> levelsOrder = new Hashtable<AOPLogger.LoggerLevel, Integer>();

	static {
		try {
			mthMap.put(LoggerLevel.DEBUG,
					Logger.class.getMethod("debug", Object.class));
			mthMap.put(LoggerLevel.ERROR,
					Logger.class.getMethod("error", Object.class));
			mthMap.put(LoggerLevel.INFO,
					Logger.class.getMethod("info", Object.class));
			mthMap.put(LoggerLevel.TRACE,
					Logger.class.getMethod("trace", Object.class));
			mthMap.put(LoggerLevel.WARN,
					Logger.class.getMethod("warn", Object.class));
			mthMap.put(LoggerLevel.FATAL,
					Logger.class.getMethod("fatal", Object.class));
			mthMapError.put(LoggerLevel.DEBUG, Logger.class.getMethod("debug",
					Object.class, Throwable.class));
			mthMapError.put(LoggerLevel.ERROR, Logger.class.getMethod("error",
					Object.class, Throwable.class));
			mthMapError.put(LoggerLevel.INFO, Logger.class.getMethod("info",
					Object.class, Throwable.class));
			mthMapError.put(LoggerLevel.TRACE, Logger.class.getMethod("trace",
					Object.class, Throwable.class));
			mthMapError.put(LoggerLevel.WARN, Logger.class.getMethod("warn",
					Object.class, Throwable.class));
			mthMapError.put(LoggerLevel.FATAL, Logger.class.getMethod("fatal",
					Object.class, Throwable.class));
			levelsOrder.put(LoggerLevel.DEBUG, Level.DEBUG_INT);
			levelsOrder.put(LoggerLevel.ERROR, Level.ERROR_INT);
			levelsOrder.put(LoggerLevel.FATAL, Level.FATAL_INT);
			levelsOrder.put(LoggerLevel.INFO, Level.INFO_INT);
			levelsOrder.put(LoggerLevel.WARN, Level.WARN_INT);
			levelsOrder.put(LoggerLevel.TRACE, Level.TRACE_INT);

		} catch (NoSuchMethodException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	before() : logger() {
		Class target = thisJoinPoint.getTarget().getClass();
		StringBuilder message = new StringBuilder("Before method ")
				.append(target.toString()).append("::")
				.append(thisJoinPoint.getSignature().getName());
		try {
			log(thisJoinPoint, LoggerWhen.ON_ENTER, message.toString());
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public void log(JoinPoint joinPoint, LoggerWhen when, String message)
			throws IllegalAccessException, InvocationTargetException {
		AOPLogger l = getLoggerAnnotation(joinPoint);
		l = (l == null ? getLoggerClassAnnotation(joinPoint) : l);
		if (l != null) {
			LoggerWhen[] lws = l.loggerWhen();
			LoggerLevel[] levels = l.loggerLevel();
			int greaterLevel = 0;
			LoggerLevel currentLevel = null;
			// calcolo qual'è il livello più basso di "loggaggio"
			for (LoggerLevel level : levels) {
				int tmp = levelsOrder.get(level);
				if (tmp > greaterLevel) {
					greaterLevel = tmp;
					currentLevel = level;
				}
			}
			if (doLog(lws, when)) {
				String[] names = l.name();
				for (int i = 0; i < names.length; i++) {
					String name = names[i];
					Logger log = Logger.getLogger(name);
					Class target = joinPoint.getTarget().getClass();
					String methodName = joinPoint.getSignature().getName();

					mthMap.get(currentLevel).invoke(log, message);

				}
			}
		}
	}

	private AOPLogger getLoggerClassAnnotation(JoinPoint joinPoint) {

		return joinPoint.getTarget().getClass().getAnnotation(AOPLogger.class);
	}

	Object around()  : loggerMethod(){
		Class target = thisJoinPoint.getTarget().getClass();
		StringBuilder message = new StringBuilder("Method parameters ")
				.append(target.toString()).append("::")
				.append(thisJoinPoint.getSignature().getName()).append("\n");
		Object[] values = thisJoinPoint.getArgs();
		MethodSignature ms = (MethodSignature) thisJoinPoint.getSignature();
		String[] parameterNames = ms.getParameterNames();
		Class[] parameterTypes = ms.getParameterTypes();
		for (int i = 0; i < parameterTypes.length; i++) {
			Class type = parameterTypes[i];
			String name = parameterNames[i];
			Object value = values[i];
			message.append("\t[" + i + "] - " + type.toString());
			message.append("\t\t" + name + ": " + value);
		}
		try {
			log(thisJoinPoint, LoggerWhen.PARAMS, message.toString());
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return proceed();
	}

	Object around() : logger()  {
		Class target = thisJoinPoint.getTarget().getClass();
		String name = thisJoinPoint.getSignature().getName();
		StringBuilder message = new StringBuilder("Method returns ")
				.append(target.toString()).append("::")
				.append(thisJoinPoint.getSignature().getName())
				.append("\n\t\t");

		Object retValue = proceed();
		message.append(retValue == null ? "null" : retValue.toString());
		try {
			log(thisJoinPoint, LoggerWhen.ON_RETURN, message.toString());
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return retValue;

	}

	after()  : logger(){
		Class target = thisJoinPoint.getTarget().getClass();
		StringBuilder message = new StringBuilder("After method ")
				.append(target.toString()).append("::")
				.append(thisJoinPoint.getSignature().getName());
		try {
			log(thisJoinPoint, LoggerWhen.ON_EXIT, message.toString());
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	private boolean doLog(LoggerWhen[] lws, LoggerWhen when) {
		for (int i = 0; i < lws.length; i++) {
			LoggerWhen loggerWhen = lws[i];
			if (loggerWhen.equals(LoggerWhen.ANY)) {
				return true;
			}
			if (loggerWhen.equals(when)) {
				return true;
			}
		}
		return false;
	}

	private AOPLogger getLoggerAnnotation(JoinPoint joinPoint) {
		MethodSignature ms = (MethodSignature) joinPoint.getSignature();
		Method method = ms.getMethod();
		AOPLogger logger = method.getAnnotation(AOPLogger.class);
		return logger;
	}

}
